package gmodel

import (
	"context"
	"fmt"
	"gitee.com/851537/gmodel/rds"
	"github.com/bsm/redislock"
	"github.com/google/uuid"
	"reflect"
	"time"
)

type IRedisLock interface {
	IBase
	SetLockKey(key interface{}) IRedisLock
	GetLockKey() string

	Lock(child IRedisLock, timeout ...int64) (lock *redislock.Lock, err error)
	Unlock(lock *redislock.Lock)
}

// 实体类继承该类,便拥有了锁功能,子类不需要实现任何方法
type BaseRedisLock struct {
	Base
	key string //未显式调用SetLockKey时，默认为子对象的ID值，see RedisLockName 方法
}

func (this *BaseRedisLock) GetId() int64 {
	return int64(uuid.New().ID())
}

func (this *BaseRedisLock) SetLockKey(key interface{}) IRedisLock {
	this.key = fmt.Sprintf("%v", key)
	return this
}

func (this *BaseRedisLock) GetLockKey() string {
	return this.key
}

func (this *BaseRedisLock) RedisLockName(child IRedisLock) string {
	var key string
	if key = child.GetLockKey(); key == "" { //未显式调用SetLockKey时，默认为子对象的ID值
		child.SetLockKey(child.GetId())
	}
	return fmt.Sprintf("lock.%s.%v", reflect.ValueOf(child).Elem().Type().Name(), child.GetLockKey())
}

// maxLockTime 最长持有锁的时间, 单位:秒
func (this *BaseRedisLock) Lock(child IRedisLock, maxLockTime ...int64) (lock *redislock.Lock, err error) {
	client := redislock.New(rds.ClientIns())
	lockTime := time.Second * 5 //default
	if len(maxLockTime) > 0 {
		lockTime = time.Second * time.Duration(maxLockTime[0])
	}

	// Retry every 500ms, for up-to a minute
	backoff := redislock.LinearBackoff(500 * time.Millisecond)
	ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Minute))
	defer cancel()

	return client.Obtain(ctx, this.RedisLockName(child), lockTime, &redislock.Options{
		RetryStrategy: backoff,
	})
}

func (this *BaseRedisLock) Unlock(lock *redislock.Lock) {
	_ = lock.Release(context.Background())
}
